﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Solidi
{
	/// <remarks>
	/// Thread-safe
	/// </remarks>
	public sealed class DependencyLifetime : IDependencyLifetime
	{
		private IDictionary<object, DependencyObject> _instances = new ConcurrentDictionary<object, DependencyObject>(ObjectReferenceEqualityComparer.Default);
		private ConcurrentDictionary<DependencyObject, DependencyObject> _tokens = new ConcurrentDictionary<DependencyObject, DependencyObject>();
        private ConcurrentDictionary<object, object> _locks = new ConcurrentDictionary<object, object>(ObjectReferenceEqualityComparer.Default);
		
		public bool TryAcquire(object graphId, IDependencyContainer container, IDependency dependency, out object instance)
		{
            if (dependency == null)
            {
                throw new ArgumentNullException("dependency");
            }

            object locker = _locks.GetOrAdd(dependency, new object());

			DependencyObject obj = new DependencyObject(dependency, dependency.Lifestyle.AcquireToken(graphId), locker);

            lock (locker)
			{
				obj = _tokens.GetOrAdd(obj, obj);

				obj.Pin(); // keep alive until obj lock just in case an overlapping thread tries to discard
			}

			// overlapping calls on different threads before the dependency object lock are not a problem by design
			lock (obj)
			{
				obj.Unpin();

				if (obj.Aquire(container, out instance))
				{
					_instances.Add(instance, obj);
				}
			}

			return instance != null;
		}

		public bool TryAcquire(IDependencyContainer container, IDependency dependency, out object instance)
		{
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }

			object graphId = new object();

			IDependencyResolver resolver = container.Resolver;
			IDependencyComposer composer = container.Composer;
			IDependencyLifetime lifetime = container.Lifetime;

			resolver = new GraphDependencyResolver(graphId, resolver);
			composer = new ContextualDependencyComposer(graphId, composer);
			lifetime = new ContextualDependencyLifetime(graphId, lifetime);

			container = new DependencyContainer(container.Registry, resolver, composer, lifetime);

			return lifetime.TryAcquire(graphId, container, dependency, out instance);
		}

		public bool TryDiscard(IDependencyContainer container, object instance)
		{
			DependencyObject obj;

			bool found = _instances.TryGetValue(instance, out obj);

			if (found)
			{
				// overlapping calls on different threads before the dependency object lock are not a problem by design
				lock (obj)
				{
					if (obj.Discard(container, out found))
					{
						Remove(obj, instance);
					}
				}
			}

			return found;
		}

		private void Remove(DependencyObject obj, object instance)
		{
			_instances.Remove(instance);

			lock (obj.Locker)
			{
				if (!obj.Pinned)
				{
					DependencyObject value;

					_tokens.TryRemove(obj, out value);
				}
			}
		}

		public void DiscardAll(IDependencyContainer container, bool disposing)
		{
			foreach (KeyValuePair<object, DependencyObject> pair in _instances.ToArray())
			{
				object instance = pair.Key;

				DependencyObject obj = pair.Value;

				// overlapping calls on different threads are not a problem outside the dependency object lock by design
				lock (obj)
				{
					if (obj.DiscardAll(container, disposing))
					{
						Remove(obj, instance);
					}
				}
			}
		}
	}
}
